package com.gitee.wsl.func.factory.sample

import com.gitee.wsl.func.factory.Factory
import java.lang.reflect.Constructor
import java.lang.reflect.InvocationTargetException


/**
 * Factory implementation that creates a new object instance by reflection.
 *
 *
 * **WARNING:** from v4.1 onwards this class will **not** be serializable anymore
 * in order to prevent potential remote code execution exploits. Please refer to
 * [COLLECTIONS-580](https://issues.apache.org/jira/browse/COLLECTIONS-580)
 * for more details.
 *
 *
 * @param <T> the type of results supplied by this supplier.
 * @since 3.0
</T> */
class InstantiateFactory<T> : Factory<T> {
    /** The class to create  */
    private val iClassToInstantiate: Class<T>

    /** The constructor parameter types  */
    private val iParamTypes: Array<Class<*>>?

    /** The constructor arguments  */
    private val iArgs: Array<Any>?

    /** The constructor  */
    @Transient
    private var iConstructor: Constructor<T>? = null

    /**
     * Constructor that performs no validation.
     * Use `instantiateFactory` if you want that.
     *
     * @param classToInstantiate  the class to instantiate
     */
    constructor(classToInstantiate: Class<T>) {
        iClassToInstantiate = classToInstantiate
        iParamTypes = null
        iArgs = null
        findConstructor()
    }

    /**
     * Constructor that performs no validation.
     * Use `instantiateFactory` if you want that.
     *
     * @param classToInstantiate  the class to instantiate
     * @param paramTypes  the constructor parameter types, cloned
     * @param args  the constructor arguments, cloned
     */
    constructor(classToInstantiate: Class<T>, paramTypes: Array<Class<*>>, args: Array<Any>) {
        iClassToInstantiate = classToInstantiate
        iParamTypes = paramTypes.clone()
        iArgs = args.clone()
        findConstructor()
    }

    /**
     * Creates an object using the stored constructor.
     *
     * @return the new object
     */
    public override fun create(): T {
        // needed for post-serialization
        if (iConstructor == null) {
            findConstructor()
        }

        try {
            return iConstructor!!.newInstance(iArgs)
        } catch (ex: InstantiationException) {
            throw FunctorException("InstantiateFactory: InstantiationException", ex)
        } catch (ex: IllegalAccessException) {
            throw FunctorException("InstantiateFactory: Constructor must be public", ex)
        } catch (ex: InvocationTargetException) {
            throw FunctorException("InstantiateFactory: Constructor threw an exception", ex)
        }
    }

    /**
     * Find the Constructor for the class specified.
     */
    private fun findConstructor() {
        try {
            iConstructor = if(iParamTypes == null)
                               iClassToInstantiate.getConstructor()
                           else
                               iClassToInstantiate.getConstructor(*iParamTypes )
        } catch (ex: NoSuchMethodException) {
            throw IllegalArgumentException("InstantiateFactory: The constructor must exist and be public ")
        }
    }

    companion object {
        /**
         * Factory method that performs validation.
         *
         * @param <T>  the type the factory creates
         * @param classToInstantiate  the class to instantiate, not null
         * @param paramTypes  the constructor parameter types, cloned
         * @param args  the constructor arguments, cloned
         * @return a new instantiate factory
         * @throws NullPointerException if classToInstantiate is null
         * @throws IllegalArgumentException if paramTypes does not match args
        </T> */
        fun <T> instantiateFactory(
            classToInstantiate: Class<T>,
            paramTypes: Array<Class<*>>?,
            args: Array<Any>,
        ): Factory<T?> {
            require(!(paramTypes == null && args != null || paramTypes != null && args == null || paramTypes != null && args != null && paramTypes.size != args.size)) { "Parameter types must match the arguments" }
            if (paramTypes == null || paramTypes.isEmpty()) {
                return InstantiateFactory<T>(classToInstantiate)
            }
            return InstantiateFactory<T>(classToInstantiate, paramTypes, args)
        }
    }
}
